/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.objer;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.util.List;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.objer.objectid.PackedObjectId;

import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.codes.SHA1;

/**
 * 包索引文件写入
 *
 * @version v1.0.0 @author zouzhigang 2017-5-19 新建与整理
 */
public class PackIndexWriter implements GitConstants
{
    private final DigestOutputStream out;
    private List<? extends PackedObjectId> entries;
    
    public PackIndexWriter(OutputStream dst)
    {
        out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst : new BufferedOutputStream(dst), SHA1.getInstance());
    }

    /**
     * 写入对象列表
     * 
     * @param objList           对象列表
     * @param packChecksum      包校验码
     * @throws IOException      IO异常
     */
    public void write(final List<? extends PackedObjectId> objList, final byte[] packChecksum) throws IOException
    {
        entries = objList;
        
        writeHeader();
        writeFanoutTable();
        writeObjectIds();
        writeCRCs();
        writeOffset32();
        writeOffset64();
        writeChecksumTrailer(packChecksum);
        
        out.flush();
    }

    /** 写入索引文件头部8字节 */
    private void writeHeader() throws IOException
    {
        out.write(TOC);
        out.write(Bytes.BU.toBytes(PACK_VERSION_GENERATED));
    }

    /** 写入分段表 */
    private void writeFanoutTable() throws IOException
    {
        int[] fanout = new int[FANOUT];
        for (PackedObjectId po : entries)
        {//每段数目
            fanout[po.getFirstByte()]++;
        }
        
        for (int i=1;i<FANOUT;i++)
        {//后一个是前一个值+数目
            fanout[i] += fanout[i - 1];
        }
        
        for (int num : fanout)
        {//依次写入到流，得到如000111222 ... 666这种的
            out.write(Bytes.BU.toBytes(num));
        }
    }
    
    /** 写入所有对象编号 */
    private void writeObjectIds() throws IOException
    {
        for (PackedObjectId po : entries){
            po.copyRawTo(out);
        }
    }
    
    /** 写入所有对象CRC校验码 */
    private void writeCRCs() throws IOException
    {
        for (PackedObjectId po : entries){
            out.write(Bytes.BU.toBytes(po.getCRC()), 0, 4);
        }
    }
    
    /** 写入所有对象偏移值 */
    private void writeOffset32() throws IOException
    {
        int o64 = 0;
        for (PackedObjectId po : entries)
        {
            long off = po.getOffset();
            if (off > Integer.MAX_VALUE)
            {//有64位时，传入对应数目的负数值
                off = IS_O64 | o64++;
            }
            
            out.write(Bytes.BU.toBytes((int)off));
        }
    }
    
    /** 写入所有对象偏移值64位 */
    private void writeOffset64() throws IOException
    {
        for (PackedObjectId po : entries)
        {
            long off = po.getOffset();
            if (off > Integer.MAX_VALUE)
            {
                out.write(Bytes.BU.toBytes(off));
            }
        }
    }

    /** 写入包校验码和自己的索引校验码 */
    protected void writeChecksumTrailer(byte[] packChecksum) throws IOException
    {
        out.write(packChecksum);
        out.on(false);
        out.write(out.getMessageDigest().digest());
    }
}
